#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_spi.h"
#include "lcd.h"
#include "lcd_font.h"

static void send_cmd(char cmd)
{
    GPIO_ResetBits(GPIOA, GPIO_Pin_12);
    SPI_I2S_SendData(SPI2, cmd);
    while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE));
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY));
}

static void send_data(char data)
{
    GPIO_SetBits(GPIOA, GPIO_Pin_12);
    SPI_I2S_SendData(SPI2, data);
    while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE));
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY));
}

static void send_data_w(int data)
{
    char high = data >> 8, low = data;
    GPIO_SetBits(GPIOA, GPIO_Pin_12);
    SPI_I2S_SendData(SPI2, high);
    while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE));
    SPI_I2S_SendData(SPI2, low);
    while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE));
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY));
}

static void send_data_w_repeat(int data, int repeat)
{
    char high = data >> 8, low = data;
    GPIO_SetBits(GPIOA, GPIO_Pin_12);
    for (int i = 0; i < repeat; i++)
    {
        SPI_I2S_SendData(SPI2, high);
        while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE));
        SPI_I2S_SendData(SPI2, low);
        while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE));
    }
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY));
}

static void send_data_w_bitmap(int data0, int data1, const char * bitmap, int bit_cnt)
{
    char high[2] = { data0 >> 8, data1 >> 8 };
    char low[2] = { data0, data1 };
    GPIO_SetBits(GPIOA, GPIO_Pin_12);
    for (int i = 0; i < bit_cnt; i++)
    {
        int bit = (bitmap[i / 8] >> (7 - (i % 8))) & 1;
        SPI_I2S_SendData(SPI2, high[bit]);
        while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE));
        SPI_I2S_SendData(SPI2, low[bit]);
        while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE));
    }
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY));
}

void lcd_init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

    GPIO_InitTypeDef GPIO_InitStruct =
    {
        .GPIO_Speed = GPIO_Speed_50MHz
    };

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_15;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    GPIO_SetBits(GPIOA, GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_15);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_15;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    SPI_InitTypeDef SPI_InitStruct =
    {
        .SPI_Direction = SPI_Direction_1Line_Tx,
        .SPI_Mode = SPI_Mode_Master,
        .SPI_DataSize = SPI_DataSize_8b,
        .SPI_CPOL = SPI_CPOL_Low,
        .SPI_CPHA = SPI_CPHA_1Edge,
        .SPI_NSS = SPI_NSS_Hard,
        .SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4,
        .SPI_FirstBit = SPI_FirstBit_MSB,
        .SPI_CRCPolynomial = 1
    };
    SPI_Init(SPI2, &SPI_InitStruct);
    SPI_SSOutputCmd(SPI2, ENABLE);
    SPI_Cmd(SPI2, ENABLE);

    for (int i = 0; i < 0xffff; i++);
    GPIO_ResetBits(GPIOB, GPIO_Pin_1);
    for (int i = 0; i < 0xffff; i++);
    GPIO_SetBits(GPIOB, GPIO_Pin_1);
    for (int i = 0; i < 0xffff; i++);
    send_cmd(0x01);
    for (int i = 0; i < 0xffff; i++);
    send_cmd(0x11);
    send_cmd(0x3a); send_data(0x05);
    send_cmd(0x36); send_data(0xc0);
    send_cmd(0x29);
}

void lcd_fill_rect(int x, int y, int width, int height, int color)
{
    int size = width * height;
    send_cmd(0x2a);
    send_data_w(x);
    send_data_w(x + width - 1);
    send_cmd(0x2b);
    send_data_w(y);
    send_data_w(y + height - 1);
    send_cmd(0x2c);
    send_data_w_repeat(color, size);
}

void lcd_print_char(int x, int y, char c, int back_color, int fore_color)
{
    send_cmd(0x2a);
    send_data_w(x);
    send_data_w(x + 8 - 1);
    send_cmd(0x2b);
    send_data_w(y);
    send_data_w(y + 16 - 1);
    send_cmd(0x2c);
    send_data_w_bitmap(back_color, fore_color, font + (c - ' ') * 16, 16 * 8);
}

void lcd_print_char_wide(int x, int y, int wc, int back_color, int fore_color)
{
    send_cmd(0x2a);
    send_data_w(x);
    send_data_w(x + 16 - 1);
    send_cmd(0x2b);
    send_data_w(y);
    send_data_w(y + 16 - 1);
    send_cmd(0x2c);
    send_data_w_bitmap(back_color, fore_color, font_wide + wc * 32, 16 * 16);
}
